import sys
import os
import time
import datetime
import serial
import serial.tools.list_ports
import struct
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QComboBox, QPushButton, QTextEdit, QGroupBox,
                             QFormLayout, QSpinBox, QDoubleSpinBox, QMessageBox,
                             QFileDialog, QCheckBox, QDialog, QSizePolicy, QProgressDialog)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDateTime, QTimer
from PyQt5.QtGui import QColor, QTextCursor, QIcon, QPainter, QBrush, QPen

# 关键修改：使用不需要kiwisolver的后端
import matplotlib

matplotlib.use('Agg')  # 非交互式后端，不依赖kiwisolver
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt

# 设置matplotlib中文字体支持（替换为更通用的中文字体）
matplotlib.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题


class ConnectionStatusLED(QLabel):
    """连接状态指示灯"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(20, 20)
        self.setStatus(False)  # 默认断开状态

    def setStatus(self, connected):
        """设置状态：True为连接（红色），False为断开（灰色）"""
        self.connected = connected
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        # 绘制圆形指示灯
        radius = 8
        center = self.rect().center()

        if self.connected:
            painter.setBrush(QBrush(QColor(220, 50, 50)))  # 红色
        else:
            painter.setBrush(QBrush(QColor(150, 150, 150)))  # 灰色

        painter.setPen(QPen(Qt.NoPen))
        painter.drawEllipse(center, radius, radius)


class ModbusRTU:
    """MODBUS RTU协议处理类"""

    def __init__(self, serial_port):
        self.serial = serial_port
        self.timeout = 1.0  # 超时时间1秒

    def calculate_crc(self, data):
        """计算MODBUS CRC校验"""
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc.to_bytes(2, byteorder='little')

    def read_holding_registers(self, slave_addr, start_addr, reg_count, timeout=1.0):
        """读取保持寄存器，可指定超时时间"""
        # 构建请求帧
        func_code = 0x03
        data = bytearray([slave_addr, func_code])
        data.extend(start_addr.to_bytes(2, byteorder='big'))
        data.extend(reg_count.to_bytes(2, byteorder='big'))

        # 添加CRC校验
        crc = self.calculate_crc(data)
        data.extend(crc)

        # 清空输入缓冲区
        self.serial.flushInput()

        # 发送请求
        try:
            self.serial.write(data)
            self.serial.flush()
        except Exception as e:
            return None, f"发送数据失败: {str(e)}"

        # 等待响应
        response_length = 5 + 2 * reg_count  # 响应帧长度
        response = bytearray()

        start_time = time.time()
        while len(response) < response_length and (time.time() - start_time) < timeout:
            if self.serial.in_waiting > 0:
                response.extend(self.serial.read(self.serial.in_waiting))
            time.sleep(0.01)  # 短暂延迟，减少CPU占用

        if len(response) < response_length:
            return None, f"未收到完整响应，仅收到 {len(response)} 字节"

        # 验证CRC
        received_crc = response[-2:]
        calculated_crc = self.calculate_crc(response[:-2])
        if received_crc != calculated_crc:
            return None, f"CRC校验失败，收到: {received_crc.hex()}，计算: {calculated_crc.hex()}"

        # 验证从站地址和功能码
        if response[0] != slave_addr:
            return None, f"从站地址不匹配，收到: {response[0]}, 期望: {slave_addr}"

        if response[1] != func_code:
            if (response[1] & 0x80) == 0x80:
                error_code = response[2]
                return None, f"从站返回错误: 错误码 {error_code}"
            else:
                return None, f"功能码不匹配，收到: {response[1]}, 期望: {func_code}"

        # 验证数据长度
        if response[2] != 2 * reg_count:
            return None, f"数据长度不匹配，收到: {response[2]}, 期望: {2 * reg_count}"

        # 解析数据
        registers = []
        for i in range(reg_count):
            reg_value = (response[3 + 2 * i] << 8) | response[4 + 2 * i]
            # 处理有符号整数
            if reg_value & 0x8000:
                reg_value -= 0x10000
            registers.append(reg_value)

        return registers, "成功"


class SerialThread(QThread):
    """串口通信线程，避免UI卡顿"""
    # 新增：添加success参数标识数据是否读取成功
    data_received = pyqtSignal(float, str, str, str, bool)  # 压力值, 单位, 时间戳, 状态, 是否成功
    log_message = pyqtSignal(str, str)  # 消息内容, 类型(receive/send/info/error)
    status_changed = pyqtSignal(str)  # 状态变化

    def __init__(self, serial_port, slave_addr, auto_read=False, read_interval=1000):
        super().__init__()
        self.serial_port = serial_port
        self.slave_addr = slave_addr
        self.auto_read = auto_read
        self.read_interval = read_interval
        self.running = False
        self.modbus = None
        self.pressure_unit = ""
        self.decimal_places = 0
        self.stop_flag = False

    def run(self):
        self.running = True
        self.modbus = ModbusRTU(self.serial_port)

        # 首先读取单位和小数点位数
        self.read_unit_and_decimal()

        while self.running and not self.stop_flag:
            # 读取压力数据
            self.read_pressure_data()

            if self.auto_read:
                # 自动读取模式下等待指定时间
                # 改进：使用精确的时间控制
                wait_until = time.time() + self.read_interval / 1000.0
                while time.time() < wait_until and not self.stop_flag:
                    time.sleep(0.001)  # 更高精度的等待
            else:
                # 手动读取模式下只读取一次就暂停
                self.running = False

        self.status_changed.emit("就绪")

    def read_unit_and_decimal(self):
        """读取单位和小数点位数"""
        # 改进：使用毫秒级时间戳
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  # 保留3位毫秒

        # 读取小数点位数 (0x0007)
        self.log_message.emit(f"[{timestamp}] 发送: 读取小数点位数 (地址: {self.slave_addr}, 寄存器: 0x0007)", "send")
        regs, msg = self.modbus.read_holding_registers(self.slave_addr, 0x0007, 1)

        if regs is not None and len(regs) > 0:
            self.decimal_places = regs[0]
            if self.decimal_places < 0 or self.decimal_places > 3:
                self.log_message.emit(f"[{timestamp}] 警告: 小数点位数超出范围 (0-3)，值为 {self.decimal_places}",
                                      "error")
                self.decimal_places = 0  # 使用默认值
            self.log_message.emit(f"[{timestamp}] 收到: 小数点位数 = {self.decimal_places}", "receive")
        else:
            self.log_message.emit(f"[{timestamp}] 错误: 读取小数点位数失败 - {msg}", "error")
            self.decimal_places = 0  # 使用默认值

        # 读取单位 (0x0006)
        self.log_message.emit(f"[{timestamp}] 发送: 读取单位 (地址: {self.slave_addr}, 寄存器: 0x0006)", "send")
        regs, msg = self.modbus.read_holding_registers(self.slave_addr, 0x0006, 1)

        unit_map = {
            0: "Pa", 1: "kPa", 2: "MPa", 3: "mmH2O", 4: "mH2O",
            5: "Bar", 6: "Psi", 7: "Atm", 8: "Kgf/cm2", 9: "Mm",
            10: "M", 11: "℃", 12: "℉"
        }

        if regs is not None and len(regs) > 0:
            unit_code = regs[0]
            self.pressure_unit = unit_map.get(unit_code, f"未知 ({unit_code})")
            self.log_message.emit(f"[{timestamp}] 收到: 单位 = {self.pressure_unit}", "receive")

            # 检查是否是温度单位
            if unit_code in [11, 12]:
                self.log_message.emit(f"[{timestamp}] 错误: 选择了温度单位，无法读取压力数据", "error")
                self.status_changed.emit("错误: 温度单位")
                QMessageBox.warning(None, "单位错误", "此压力表的单位选择的是温度单位，无法读取压力数据，请重新配置表。")
        else:
            self.log_message.emit(f"[{timestamp}] 错误: 读取单位失败 - {msg}", "error")
            self.pressure_unit = "未知"

        # 发送当前设备信息到日志状态栏
        device_info = f"表地址: {self.slave_addr} | 小数点位数: {self.decimal_places} | 单位: {self.pressure_unit}"
        self.log_message.emit(device_info, "status")

    def read_pressure_data(self):
        """读取压力数据"""
        # 改进：使用毫秒级时间戳
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  # 保留3位毫秒

        # 检查是否是温度单位
        if self.pressure_unit in ["℃", "℉"]:
            self.log_message.emit(f"[{timestamp}] 错误: 设备使用温度单位，无法读取压力数据", "error")
            # 发送数据失败信号
            self.data_received.emit(0.0, self.pressure_unit, timestamp, "错误: 温度单位", False)
            return

        self.log_message.emit(f"[{timestamp}] 发送: 读取压力数据 (地址: {self.slave_addr}, 寄存器: 0x0001)", "send")
        regs, msg = self.modbus.read_holding_registers(self.slave_addr, 0x0001, 1)

        if regs is not None and len(regs) > 0:
            raw_value = regs[0]
            self.log_message.emit(f"[{timestamp}] 收到: 原始压力值 = {raw_value}", "receive")

            # 根据小数点位数转换
            scale_factor = 10 ** self.decimal_places
            pressure_value = raw_value / scale_factor

            # 发送数据到主线程，标记为成功
            self.data_received.emit(pressure_value, self.pressure_unit, timestamp, "成功", True)
            self.status_changed.emit("数据读取成功")
        else:
            self.log_message.emit(f"[{timestamp}] 错误: 读取压力数据失败 - {msg}", "error")
            self.status_changed.emit(f"错误: {msg}")
            # 发送数据失败信号
            self.data_received.emit(0.0, self.pressure_unit, timestamp, f"错误: {msg}", False)

    def stop(self):
        """停止线程"""
        self.stop_flag = True
        self.wait()


class AddressSearchThread(QThread):
    """自动搜索从站地址的线程，找到一个地址后立即停止"""
    progress_updated = pyqtSignal(int)  # 进度更新信号
    address_found = pyqtSignal(int)  # 找到地址信号
    search_finished = pyqtSignal(list)  # 搜索完成信号
    log_message = pyqtSignal(str)  # 日志消息信号

    def __init__(self, serial_port):
        super().__init__()
        self.serial_port = serial_port
        self.modbus = ModbusRTU(serial_port)
        self.stop_flag = False
        self.found_addresses = []

    def run(self):
        """执行地址搜索，找到第一个有效地址后立即停止"""
        total_addresses = 255
        self.found_addresses = []

        for addr in range(1, total_addresses + 1):
            if self.stop_flag:
                break

            # 更新进度
            progress = int((addr / total_addresses) * 100)
            self.progress_updated.emit(progress)

            # 记录当前地址的搜索状态
            self.log_message.emit(f"正在搜索地址 {addr}...")

            # 每个地址最多尝试3次
            success = False
            for attempt in range(3):
                if self.stop_flag:
                    break

                # 发送读取请求：功能码0x03，寄存器起始地址0x0002，寄存器数量1
                regs, msg = self.modbus.read_holding_registers(
                    slave_addr=addr,
                    start_addr=0x0002,
                    reg_count=1,
                    timeout=1.0  # 每次等待最多1秒
                )

                if regs is not None:
                    # 成功找到有效地址
                    success = True
                    self.log_message.emit(f"地址 {addr} 响应成功 (尝试 {attempt + 1}/3)")
                    break
                else:
                    self.log_message.emit(f"地址 {addr} 无响应 (尝试 {attempt + 1}/3): {msg}")

            if success:
                self.found_addresses.append(addr)
                self.address_found.emit(addr)
                # 找到一个有效地址后立即停止搜索
                self.log_message.emit(f"已找到有效地址 {addr}，停止搜索")
                break

        # 搜索完成
        self.search_finished.emit(self.found_addresses)

    def stop_search(self):
        """停止搜索"""
        self.stop_flag = True


class ParitySearchThread(QThread):
    """自动搜索校验方式的线程"""
    search_finished = pyqtSignal(int)  # 搜索完成信号，返回校验方式代码
    log_message = pyqtSignal(str)  # 日志消息信号

    def __init__(self, serial_port, slave_addr):
        super().__init__()
        self.serial_port = serial_port
        self.slave_addr = slave_addr
        self.modbus = ModbusRTU(serial_port)
        self.stop_flag = False

    def run(self):
        """执行校验方式搜索"""
        self.log_message.emit(f"开始搜索校验方式 (从站地址: {self.slave_addr})")

        # 最多尝试3次
        for attempt in range(3):
            if self.stop_flag:
                break

            self.log_message.emit(f"读取校验方式 (尝试 {attempt + 1}/3)")

            # 发送读取请求：功能码0x03，寄存器起始地址0x0004，寄存器数量1
            regs, msg = self.modbus.read_holding_registers(
                slave_addr=self.slave_addr,
                start_addr=0x0004,
                reg_count=1,
                timeout=1.0  # 每次等待最多1秒
            )

            if regs is not None and len(regs) > 0:
                parity_code = regs[0]
                self.log_message.emit(f"成功读取校验方式: 代码 {parity_code}")
                self.search_finished.emit(parity_code)
                return
            else:
                self.log_message.emit(f"读取校验方式失败 (尝试 {attempt + 1}/3): {msg}")

        # 如果所有尝试都失败
        self.log_message.emit("搜索校验方式失败，所有尝试均未获得有效响应")
        self.search_finished.emit(-1)  # 用-1表示失败

    def stop_search(self):
        """停止搜索"""
        self.stop_flag = True


class BaudrateSearchThread(QThread):
    """自动搜索波特率的线程"""
    search_finished = pyqtSignal(int)  # 搜索完成信号，返回波特率代码
    log_message = pyqtSignal(str)  # 日志消息信号

    def __init__(self, serial_port, slave_addr):
        super().__init__()
        self.serial_port = serial_port
        self.slave_addr = slave_addr
        self.modbus = ModbusRTU(serial_port)
        self.stop_flag = False

    def run(self):
        """执行波特率搜索"""
        self.log_message.emit(f"开始搜索波特率 (从站地址: {self.slave_addr})")

        # 最多尝试3次
        for attempt in range(3):
            if self.stop_flag:
                break

            self.log_message.emit(f"读取波特率 (尝试 {attempt + 1}/3)")

            # 发送读取请求：功能码0x03，寄存器起始地址0x0003，寄存器数量1
            regs, msg = self.modbus.read_holding_registers(
                slave_addr=self.slave_addr,
                start_addr=0x0003,
                reg_count=1,
                timeout=1.0  # 每次等待最多1秒
            )

            if regs is not None and len(regs) > 0:
                baudrate_code = regs[0]
                self.log_message.emit(f"成功读取波特率: 代码 {baudrate_code}")
                self.search_finished.emit(baudrate_code)
                return
            else:
                self.log_message.emit(f"读取波特率失败 (尝试 {attempt + 1}/3): {msg}")

        # 如果所有尝试都失败
        self.log_message.emit("搜索波特率失败，所有尝试均未获得有效响应")
        self.search_finished.emit(-1)  # 用-1表示失败

    def stop_search(self):
        """停止搜索"""
        self.stop_flag = True


class MplCanvas(FigureCanvas):
    """Matplotlib画布类，支持鼠标缩放和平移"""

    def __init__(self, parent=None, width=6, height=4, dpi=100, is_preview=False):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.fig.add_subplot(111)
        self.is_preview = is_preview  # 标识是否为预览图
        super(MplCanvas, self).__init__(self.fig)
        self.fig.tight_layout()

        # 启用鼠标交互
        self.axes.set_navigate(True)
        self.axes.set_navigate_mode('PAN')  # 默认平移模式

        # 存储缩放前的视图范围，用于重置视图
        self.original_xlim = None
        self.original_ylim = None

        # 预览图特殊设置
        if self.is_preview:
            self.axes.tick_params(axis='both', which='major', labelsize=8)  # 缩小预览图刻度
            self.fig.subplots_adjust(left=0.1, bottom=0.15, right=0.95, top=0.9)  # 调整边距


class PlotDialog(QDialog):
    """波形图弹窗对话框，支持鼠标交互"""

    def __init__(self, x_data, y_data, timestamps, title="压力波形图", x_label="时间 (秒)", y_label="压力值",
                 parent=None):
        super().__init__(parent)
        self.setWindowTitle(title)
        self.setGeometry(200, 200, 1000, 700)

        layout = QVBoxLayout(self)

        # 创建画布和导航工具栏
        self.canvas = MplCanvas(self, width=10, height=7, dpi=100)
        self.toolbar = NavigationToolbar(self.canvas, self)
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)

        # 添加按钮
        btn_layout = QHBoxLayout()

        self.reset_view_btn = QPushButton("重置视图")
        self.reset_view_btn.clicked.connect(self.reset_view)
        btn_layout.addWidget(self.reset_view_btn)

        self.enable_annotations_btn = QPushButton("显示数据点时间")
        self.enable_annotations_btn.setCheckable(True)
        self.enable_annotations_btn.clicked.connect(self.toggle_annotations)
        self.annotations_enabled = False
        btn_layout.addWidget(self.enable_annotations_btn)

        self.save_btn = QPushButton("保存波形图")
        self.save_btn.clicked.connect(self.save_plot)
        btn_layout.addWidget(self.save_btn)

        self.close_btn = QPushButton("关闭")
        self.close_btn.clicked.connect(self.close)
        btn_layout.addWidget(self.close_btn)

        layout.addLayout(btn_layout)

        # 存储数据用于标注
        self.timestamps = timestamps

        # 绘制图形
        self.plot_data(x_data, y_data, title, x_label, y_label)

        # 存储数据用于保存和重置视图
        self.x_data = x_data
        self.y_data = y_data
        self.plot_title = title
        self.x_label = x_label
        self.y_label = y_label
        self.annotations = []  # 存储标注对象

    def plot_data(self, x_data, y_data, title, x_label, y_label):
        """绘制波形图数据"""
        self.canvas.axes.clear()

        # 改进：使用带标记的线条，更清晰地显示数据点
        self.line, = self.canvas.axes.plot(
            x_data, y_data, 'b-',
            marker='o', markersize=3,
            linewidth=1.5, alpha=0.8
        )

        self.canvas.axes.set_title(title)
        self.canvas.axes.set_xlabel(x_label)
        self.canvas.axes.set_ylabel(y_label)
        self.canvas.axes.grid(True, linestyle='--', alpha=0.7)

        # 保存原始视图范围
        self.canvas.original_xlim = self.canvas.axes.get_xlim()
        self.canvas.original_ylim = self.canvas.axes.get_ylim()

        self.canvas.fig.tight_layout()
        self.canvas.draw()

    def toggle_annotations(self):
        """切换数据点时间标注显示"""
        self.annotations_enabled = not self.annotations_enabled

        # 清除现有标注
        for annotation in self.annotations:
            annotation.remove()
        self.annotations = []

        if self.annotations_enabled and len(self.x_data) <= 50:  # 数据点过多时不显示标注，避免拥挤
            for x, y, ts in zip(self.x_data, self.y_data, self.timestamps):
                # 显示时间戳的时分秒部分
                time_str = ts.split(' ')[1]
                annotation = self.canvas.axes.annotate(
                    time_str, (x, y),
                    textcoords="offset points", xytext=(0, 10),
                    ha='center', fontsize=8,
                    bbox=dict(boxstyle="round,pad=0.3", fc="yellow", alpha=0.5)
                )
                self.annotations.append(annotation)

        self.canvas.draw()

    def reset_view(self):
        """重置视图到初始状态"""
        if self.canvas.original_xlim and self.canvas.original_ylim:
            self.canvas.axes.set_xlim(self.canvas.original_xlim)
            self.canvas.axes.set_ylim(self.canvas.original_ylim)
            self.canvas.draw()

    def save_plot(self):
        """保存波形图"""
        if not self.x_data or not self.y_data:
            QMessageBox.information(self, "无数据", "没有可保存的波形数据")
            return

        try:
            # 获取当前时间作为文件名
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            default_filename = f"压力波形_{timestamp}.png"

            # 打开文件对话框
            filename, _ = QFileDialog.getSaveFileName(
                self, "保存波形图",
                os.path.join(os.path.expanduser("~"), "Desktop", default_filename),
                "PNG文件 (*.png);;所有文件 (*)"
            )

            if filename:
                # 保存图像
                self.canvas.fig.savefig(filename, dpi=300, bbox_inches='tight')
                QMessageBox.information(self, "保存成功", f"波形图已保存至:\n{filename}")
        except Exception as e:
            QMessageBox.critical(self, "保存失败", f"无法保存波形图:\n{str(e)}")


class PressureMonitor(QMainWindow):
    """压力监测主窗口"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("压力监测系统")
        self.setGeometry(100, 100, 1200, 900)

        # 数据存储
        self.data_history = []  # 存储格式: (时间戳, 压力值, 单位)
        self.current_excel_file = None  # 当前Excel文件路径
        self.current_slave_addr = 1
        self.serial_port = None
        self.serial_thread = None
        self.auto_save = True  # 默认自动保存数据
        self.address_search_thread = None  # 地址搜索线程
        self.parity_search_thread = None  # 校验方式搜索线程
        self.baudrate_search_thread = None  # 波特率搜索线程

        # 创建数据保存目录
        self.create_data_directory()

        # 先初始化日志组件
        self.init_log_components()

        # 再初始化其他UI组件
        self.init_other_ui_components()

        # 初始化波形图数据
        self.plot_x = []  # 相对时间（秒）
        self.plot_y = []  # 压力值
        self.plot_timestamps = []  # 完整时间戳（用于标注）
        self.plot_start_time = None

    def create_data_directory(self):
        """创建数据保存目录"""
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        self.data_dir = os.path.join(desktop_path, "压力数据")
        if not os.path.exists(self.data_dir):
            try:
                os.makedirs(self.data_dir)
            except Exception as e:
                # 这里使用QMessageBox而不是log_message，因为日志组件可能尚未初始化
                QMessageBox.critical(None, "目录创建失败", f"无法创建数据目录: {str(e)}")
                self.data_dir = desktop_path  # 退回到桌面

    def init_log_components(self):
        """先初始化日志相关组件，确保log_text存在"""
        # 创建临时中心部件来容纳日志组件
        temp_central = QWidget()
        self.setCentralWidget(temp_central)
        temp_layout = QVBoxLayout(temp_central)

        # 日志区域
        log_group = QGroupBox("通信日志")
        log_layout = QVBoxLayout()

        # 日志文本框 - 这个必须先初始化
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        log_layout.addWidget(self.log_text)

        # 设备信息状态栏
        self.device_status = QLabel("表地址: -- | 小数点位数: -- | 单位: --")
        self.device_status.setStyleSheet("background-color: #f0f0f0; padding: 5px;")
        log_layout.addWidget(self.device_status)

        log_group.setLayout(log_layout)
        temp_layout.addWidget(log_group)

    def init_other_ui_components(self):
        """初始化其他UI组件"""
        # 主Widget和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 顶部配置区域
        config_group = QGroupBox("串口配置")
        config_layout = QFormLayout()
        config_layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
        config_layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
        config_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        config_layout.setVerticalSpacing(10)
        config_layout.setHorizontalSpacing(15)

        # 串口选择（带刷新按钮）
        port_layout = QHBoxLayout()
        self.port_combo = QComboBox()
        self.port_combo.setMaximumWidth(120)  # 限制宽度
        port_layout.addWidget(self.port_combo)

        self.refresh_port_btn = QPushButton("刷新")
        self.refresh_port_btn.setMaximumWidth(80)  # 限制宽度
        self.refresh_port_btn.setIcon(QIcon.fromTheme("view-refresh", QIcon()))
        self.refresh_port_btn.clicked.connect(self.update_com_ports)
        port_layout.addWidget(self.refresh_port_btn)

        config_layout.addRow("串口号:", port_layout)

        # 波特率选择（带自动搜索按钮）
        baudrate_layout = QHBoxLayout()
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.setMaximumWidth(120)  # 限制宽度
        baudrates = ["1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"]
        self.baudrate_combo.addItems(baudrates)
        self.baudrate_combo.setCurrentText("9600")  # 默认9600
        baudrate_layout.addWidget(self.baudrate_combo)

        self.search_baudrate_btn = QPushButton("自动搜索波特率")
        self.search_baudrate_btn.setMaximumWidth(120)  # 限制宽度
        self.search_baudrate_btn.clicked.connect(self.start_baudrate_search)
        self.search_baudrate_btn.setEnabled(False)  # 初始禁用，连接后启用
        baudrate_layout.addWidget(self.search_baudrate_btn)

        config_layout.addRow("波特率:", baudrate_layout)

        # 校验方式（带自动搜索按钮）
        parity_layout = QHBoxLayout()
        self.parity_combo = QComboBox()
        self.parity_combo.setMaximumWidth(120)  # 限制宽度
        self.parity_combo.addItems(["无校验", "奇校验", "偶校验"])
        self.parity_combo.setCurrentIndex(0)  # 默认无校验
        parity_layout.addWidget(self.parity_combo)

        self.search_parity_btn = QPushButton("自动搜索校验方式")
        self.search_parity_btn.setMaximumWidth(120)  # 限制宽度
        self.search_parity_btn.clicked.connect(self.start_parity_search)
        self.search_parity_btn.setEnabled(False)  # 初始禁用，连接后启用
        parity_layout.addWidget(self.search_parity_btn)

        config_layout.addRow("校验方式:", parity_layout)

        # 从站地址（带自动搜索按钮）
        slave_addr_layout = QHBoxLayout()
        self.slave_addr_spin = QSpinBox()
        self.slave_addr_spin.setMaximumWidth(120)  # 限制宽度
        self.slave_addr_spin.setRange(1, 255)
        self.slave_addr_spin.setValue(1)  # 默认地址1
        slave_addr_layout.addWidget(self.slave_addr_spin)

        # 添加自动搜索地址按钮
        self.search_addr_btn = QPushButton("自动搜索地址")
        self.search_addr_btn.setMaximumWidth(120)  # 限制宽度
        self.search_addr_btn.clicked.connect(self.start_address_search)
        self.search_addr_btn.setEnabled(False)  # 初始禁用，连接后启用
        slave_addr_layout.addWidget(self.search_addr_btn)

        config_layout.addRow("从站地址:", slave_addr_layout)

        # 添加提示文本
        self.search_hint_label = QLabel("自动搜索地址耗时长，请耐心等待")
        self.search_hint_label.setStyleSheet("color: #7f8c8d; font-size: 12px;")
        config_layout.addRow("", self.search_hint_label)

        # 连接/断开按钮和状态灯
        connect_layout = QHBoxLayout()
        self.connect_btn = QPushButton("连接")
        self.connect_btn.setMaximumWidth(100)  # 限制宽度
        self.connect_btn.clicked.connect(self.toggle_connection)
        connect_layout.addWidget(self.connect_btn)

        # 添加状态灯
        self.status_led = ConnectionStatusLED()
        connect_layout.addWidget(self.status_led)
        connect_layout.addStretch()  # 填充空白

        config_layout.addRow("", connect_layout)

        config_group.setLayout(config_layout)
        main_layout.addWidget(config_group)

        # 中间数据显示区域
        middle_layout = QHBoxLayout()

        # 数据显示
        data_group = QGroupBox("压力数据")
        data_layout = QVBoxLayout()

        # 压力值显示
        self.pressure_display = QLabel("0.00")
        self.pressure_display.setAlignment(Qt.AlignCenter)
        self.pressure_display.setStyleSheet("font-size: 48px; font-weight: bold; color: #2c3e50;")
        data_layout.addWidget(self.pressure_display)

        # 单位显示
        self.unit_display = QLabel("单位: --")
        self.unit_display.setAlignment(Qt.AlignCenter)
        self.unit_display.setStyleSheet("font-size: 24px; color: #7f8c8d;")
        data_layout.addWidget(self.unit_display)

        # 小数点位数显示
        self.decimal_display = QLabel("小数点位数: --")
        self.decimal_display.setAlignment(Qt.AlignCenter)
        self.decimal_display.setStyleSheet("font-size: 18px; color: #7f8c8d;")
        data_layout.addWidget(self.decimal_display)

        # 时间戳显示（改进：显示毫秒级时间）
        self.timestamp_display = QLabel("最后更新: --")
        self.timestamp_display.setAlignment(Qt.AlignCenter)
        self.timestamp_display.setStyleSheet("font-size: 14px; color: #95a5a6;")
        data_layout.addWidget(self.timestamp_display)

        # 操作按钮
        btn_layout = QHBoxLayout()

        self.read_once_btn = QPushButton("读取一次")
        self.read_once_btn.setMaximumWidth(100)  # 限制宽度
        self.read_once_btn.clicked.connect(self.read_once)
        self.read_once_btn.setEnabled(False)
        btn_layout.addWidget(self.read_once_btn)

        self.auto_read_btn = QPushButton("自动读取")
        self.auto_read_btn.setMaximumWidth(100)  # 限制宽度
        self.auto_read_btn.clicked.connect(self.toggle_auto_read)
        self.auto_read_btn.setEnabled(False)
        btn_layout.addWidget(self.auto_read_btn)

        self.save_data_btn = QPushButton("保存数据")
        self.save_data_btn.setMaximumWidth(100)  # 限制宽度
        self.save_data_btn.clicked.connect(self.save_data_to_excel)
        self.save_data_btn.setEnabled(False)
        btn_layout.addWidget(self.save_data_btn)

        self.clear_data_btn = QPushButton("清除数据")
        self.clear_data_btn.setMaximumWidth(100)  # 限制宽度
        self.clear_data_btn.clicked.connect(self.clear_data)
        self.clear_data_btn.setEnabled(False)
        btn_layout.addWidget(self.clear_data_btn)

        data_layout.addLayout(btn_layout)

        # 自动保存选项
        self.auto_save_check = QCheckBox("自动保存数据到Excel")
        self.auto_save_check.setChecked(self.auto_save)
        self.auto_save_check.stateChanged.connect(self.toggle_auto_save)
        data_layout.addWidget(self.auto_save_check)

        # 读取间隔设置（改进：支持最小100ms）
        interval_layout = QHBoxLayout()
        interval_layout.addWidget(QLabel("自动读取间隔(毫秒):"))
        self.read_interval_spin = QSpinBox()
        self.read_interval_spin.setMaximumWidth(100)  # 限制宽度
        self.read_interval_spin.setRange(100, 10000)  # 最小100ms
        self.read_interval_spin.setValue(1000)
        interval_layout.addWidget(self.read_interval_spin)
        data_layout.addLayout(interval_layout)

        data_group.setLayout(data_layout)
        middle_layout.addWidget(data_group, 1)

        # 波形图预览
        plot_group = QGroupBox("压力波形预览")
        plot_layout = QVBoxLayout()

        # 创建Matplotlib画布和导航工具栏，设置为预览模式
        self.canvas = MplCanvas(self, width=6, height=4, dpi=100, is_preview=True)
        self.plot_toolbar = NavigationToolbar(self.canvas, self)
        plot_layout.addWidget(self.plot_toolbar)
        plot_layout.addWidget(self.canvas)

        # 波形图控制按钮（放在一行）
        plot_btn_layout = QHBoxLayout()

        self.show_plot_btn = QPushButton("显示波形图")
        self.show_plot_btn.setMaximumWidth(120)
        self.show_plot_btn.clicked.connect(self.show_plot_dialog)
        self.show_plot_btn.setEnabled(False)
        plot_btn_layout.addWidget(self.show_plot_btn)

        self.import_plot_btn = QPushButton("从Excel生成波形图")
        self.import_plot_btn.setMaximumWidth(150)
        plot_btn_layout.addWidget(self.import_plot_btn)
        self.import_plot_btn.clicked.connect(self.import_from_excel)

        self.clear_small_plot_btn = QPushButton("清除预览")
        self.clear_small_plot_btn.setMaximumWidth(100)
        self.clear_small_plot_btn.clicked.connect(self.clear_plot)
        plot_btn_layout.addWidget(self.clear_small_plot_btn)

        plot_layout.addLayout(plot_btn_layout)

        plot_group.setLayout(plot_layout)
        middle_layout.addWidget(plot_group, 2)

        main_layout.addLayout(middle_layout, 3)

        # 重新添加日志区域（之前临时添加的）
        log_group = QGroupBox("通信日志")
        log_layout = QVBoxLayout()
        log_layout.addWidget(self.log_text)  # 使用已初始化的log_text
        log_layout.addWidget(self.device_status)  # 使用已初始化的device_status
        log_group.setLayout(log_layout)
        main_layout.addWidget(log_group, 2)

        # 状态栏
        self.statusBar().showMessage("就绪")

        # 初始化预览波形图
        self.init_preview_plot()

        # 初始化时刷新一次串口号列表
        self.update_com_ports()

    def init_preview_plot(self):
        """初始化预览波形图"""
        self.canvas.axes.set_title('压力变化曲线预览', fontsize=10)
        self.canvas.axes.set_xlabel('时间 (秒)', fontsize=8)
        self.canvas.axes.set_ylabel('压力值', fontsize=8)
        self.canvas.axes.grid(True, linestyle='--', alpha=0.7)
        # 改进：使用带标记的线条
        self.plot_line, = self.canvas.axes.plot([], [], 'b-', marker='o', markersize=2, linewidth=1.5, alpha=0.7)

    def update_com_ports(self):
        """更新可用串口号列表，包括物理串口和虚拟串口"""
        current_port = self.port_combo.currentText() if hasattr(self, 'port_combo') else ""
        if hasattr(self, 'port_combo'):
            self.port_combo.clear()

            # 获取所有端口
            ports = serial.tools.list_ports.comports()
            port_list = []

            for port in ports:
                port_info = f"{port.device} - {port.description}"
                port_list.append((port.device, port_info))

            # 如果没有找到端口，尝试手动检测常见的虚拟串口名称
            if not port_list:
                # 常见的虚拟串口名称模式
                virtual_port_patterns = [
                    "COM",  # Windows系统
                    "/dev/ttyUSB", "/dev/ttyACM",  # Linux系统
                    "/dev/tty.usbserial", "/dev/tty.wchusbserial"  # macOS系统
                ]

                # 检查这些模式的端口是否存在
                for pattern in virtual_port_patterns:
                    for i in range(10):  # 检查前10个可能的端口号
                        if pattern == "COM":
                            port_name = f"{pattern}{i + 1}"
                        else:
                            port_name = f"{pattern}{i}"

                        # 尝试打开端口来检测是否存在
                        try:
                            test_port = serial.Serial(port_name)
                            test_port.close()
                            port_list.append((port_name, f"{port_name} (虚拟串口)"))
                        except:
                            continue

            # 添加找到的端口到下拉列表
            for port_device, port_info in port_list:
                self.port_combo.addItem(port_info, userData=port_device)

            # 如果之前选择的端口仍然存在，则选中它
            if current_port:
                for i in range(self.port_combo.count()):
                    if self.port_combo.itemData(i) == current_port:
                        self.port_combo.setCurrentIndex(i)
                        break

        # 确保log_text已初始化再记录日志
        if hasattr(self, 'log_text'):
            port_count = self.port_combo.count() if hasattr(self, 'port_combo') else 0
            self.log_message(f"已刷新串口号列表，发现 {port_count} 个可用端口", "info")

    def toggle_connection(self):
        """切换串口连接状态"""
        if self.serial_port and self.serial_port.is_open:
            # 断开连接
            self.disconnect_serial()
            self.connect_btn.setText("连接")
            self.status_led.setStatus(False)  # 更新状态灯为灰色
            self.statusBar().showMessage("已断开连接")
            self.read_once_btn.setEnabled(False)
            self.auto_read_btn.setEnabled(False)
            self.save_data_btn.setEnabled(False)
            self.clear_data_btn.setEnabled(False)
            self.show_plot_btn.setEnabled(False)
            self.search_addr_btn.setEnabled(False)  # 断开连接后禁用搜索按钮
            self.search_parity_btn.setEnabled(False)  # 断开连接后禁用搜索按钮
            self.search_baudrate_btn.setEnabled(False)  # 断开连接后禁用搜索按钮
        else:
            # 建立连接
            if self.connect_serial():
                self.connect_btn.setText("断开")
                self.status_led.setStatus(True)  # 更新状态灯为红色
                self.statusBar().showMessage("已连接")
                self.read_once_btn.setEnabled(True)
                self.auto_read_btn.setEnabled(True)
                self.save_data_btn.setEnabled(True)
                self.clear_data_btn.setEnabled(True)
                self.show_plot_btn.setEnabled(True)
                self.search_addr_btn.setEnabled(True)  # 连接后启用搜索按钮
                self.search_parity_btn.setEnabled(True)  # 连接后启用搜索按钮
                self.search_baudrate_btn.setEnabled(True)  # 连接后启用搜索按钮

                # 初始化Excel文件
                if self.auto_save and not self.current_excel_file:
                    self.init_excel_file()

    def start_address_search(self):
        """开始自动搜索从站地址，找到一个后立即停止"""
        if not self.serial_port or not self.serial_port.is_open:
            QMessageBox.warning(self, "未连接", "请先连接到串口再进行地址搜索")
            return

        # 如果正在进行数据读取，先停止
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread = None
            self.auto_read_btn.setText("自动读取")

        # 创建进度对话框
        progress_dialog = QProgressDialog("正在搜索从站地址...", "取消", 0, 100, self)
        progress_dialog.setWindowTitle("搜索中")
        progress_dialog.setWindowModality(Qt.WindowModal)
        progress_dialog.setMinimumDuration(0)
        progress_dialog.setValue(0)

        # 创建并启动搜索线程
        self.address_search_thread = AddressSearchThread(self.serial_port)

        # 连接信号与槽
        self.address_search_thread.progress_updated.connect(progress_dialog.setValue)
        self.address_search_thread.address_found.connect(self.on_address_found)
        self.address_search_thread.log_message.connect(lambda msg: self.log_message(msg, "info"))
        self.address_search_thread.search_finished.connect(
            lambda addrs: self.on_search_finished(addrs, progress_dialog))

        # 取消按钮连接
        progress_dialog.canceled.connect(self.address_search_thread.stop_search)

        # 禁用相关控件
        self.disable_ui_during_search(True)

        # 启动线程
        self.address_search_thread.start()

        # 显示进度对话框
        progress_dialog.exec_()

    def start_parity_search(self):
        """开始自动搜索校验方式"""
        if not self.serial_port or not self.serial_port.is_open:
            QMessageBox.warning(self, "未连接", "请先连接到串口再进行校验方式搜索")
            return

        # 获取当前从站地址
        slave_addr = self.slave_addr_spin.value()

        # 如果正在进行数据读取，先停止
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread = None
            self.auto_read_btn.setText("自动读取")

        # 创建进度对话框
        progress_dialog = QProgressDialog("正在搜索校验方式...", "取消", 0, 100, self)
        progress_dialog.setWindowTitle("搜索中")
        progress_dialog.setWindowModality(Qt.WindowModal)
        progress_dialog.setMinimumDuration(0)
        progress_dialog.setValue(30)  # 固定显示中间进度

        # 创建并启动搜索线程
        self.parity_search_thread = ParitySearchThread(self.serial_port, slave_addr)

        # 连接信号与槽
        self.parity_search_thread.log_message.connect(lambda msg: self.log_message(msg, "info"))
        self.parity_search_thread.search_finished.connect(
            lambda code: self.on_parity_search_finished(code, progress_dialog))

        # 取消按钮连接
        progress_dialog.canceled.connect(self.parity_search_thread.stop_search)

        # 禁用相关控件
        self.disable_ui_during_search(True)

        # 启动线程
        self.parity_search_thread.start()

        # 显示进度对话框
        progress_dialog.exec_()

    def on_parity_search_finished(self, parity_code, progress_dialog):
        """校验方式搜索完成后的处理"""
        # 关闭进度对话框
        progress_dialog.close()

        # 恢复UI状态
        self.disable_ui_during_search(False)

        # 处理搜索结果
        parity_map = {
            0: "无校验",
            1: "奇校验",
            2: "偶校验"
        }

        if parity_code == -1:
            # 搜索失败，检查是否是地址错误
            QMessageBox.warning(self, "搜索失败", "地址错误无法搜索或未收到有效响应")
        elif parity_code in parity_map:
            parity_name = parity_map[parity_code]
            QMessageBox.information(self, "搜索完成", f"找到校验方式: {parity_name}")
            # 自动设置校验方式
            self.parity_combo.setCurrentText(parity_name)
        else:
            QMessageBox.warning(self, "搜索失败", f"收到未知的校验方式代码: {parity_code}")

        # 清理线程
        self.parity_search_thread = None

    def start_baudrate_search(self):
        """开始自动搜索波特率"""
        if not self.serial_port or not self.serial_port.is_open:
            QMessageBox.warning(self, "未连接", "请先连接到串口再进行波特率搜索")
            return

        # 获取当前从站地址
        slave_addr = self.slave_addr_spin.value()

        # 如果正在进行数据读取，先停止
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread = None
            self.auto_read_btn.setText("自动读取")

        # 创建进度对话框
        progress_dialog = QProgressDialog("正在搜索波特率...", "取消", 0, 100, self)
        progress_dialog.setWindowTitle("搜索中")
        progress_dialog.setWindowModality(Qt.WindowModal)
        progress_dialog.setMinimumDuration(0)
        progress_dialog.setValue(30)  # 固定显示中间进度

        # 创建并启动搜索线程
        self.baudrate_search_thread = BaudrateSearchThread(self.serial_port, slave_addr)

        # 连接信号与槽
        self.baudrate_search_thread.log_message.connect(lambda msg: self.log_message(msg, "info"))
        self.baudrate_search_thread.search_finished.connect(
            lambda code: self.on_baudrate_search_finished(code, progress_dialog))

        # 取消按钮连接
        progress_dialog.canceled.connect(self.baudrate_search_thread.stop_search)

        # 禁用相关控件
        self.disable_ui_during_search(True)

        # 启动线程
        self.baudrate_search_thread.start()

        # 显示进度对话框
        progress_dialog.exec_()

    def on_baudrate_search_finished(self, baudrate_code, progress_dialog):
        """波特率搜索完成后的处理"""
        # 关闭进度对话框
        progress_dialog.close()

        # 恢复UI状态
        self.disable_ui_during_search(False)

        # 处理搜索结果
        baudrate_map = {
            0: "1200",
            1: "2400",
            2: "4800",
            3: "9600",
            4: "19200",
            5: "38400",
            6: "57600"
        }

        if baudrate_code == -1:
            # 搜索失败，检查是否是地址错误
            QMessageBox.warning(self, "搜索失败", "地址错误无法搜索或未收到有效响应")
        elif baudrate_code in baudrate_map:
            baudrate_value = baudrate_map[baudrate_code]
            QMessageBox.information(self, "搜索完成", f"找到波特率: {baudrate_value}")

            # 自动设置波特率需要重新连接串口
            current_baudrate = self.baudrate_combo.currentText()
            if current_baudrate != baudrate_value:
                reply = QMessageBox.question(
                    self, "更新波特率",
                    f"需要断开并重新连接串口以应用新的波特率 {baudrate_value}，是否继续？",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes
                )

                if reply == QMessageBox.Yes:
                    # 更新波特率并重新连接
                    self.baudrate_combo.setCurrentText(baudrate_value)
                    self.toggle_connection()  # 断开连接
                    self.toggle_connection()  # 重新连接
        else:
            QMessageBox.warning(self, "搜索失败", f"收到未知的波特率代码: {baudrate_code}")

        # 清理线程
        self.baudrate_search_thread = None

    def on_address_found(self, address):
        """找到有效地址时的处理"""
        self.log_message(f"找到有效从站地址: {address}", "info")
        # 将找到的地址设置为当前地址
        self.slave_addr_spin.setValue(address)
        self.current_slave_addr = address

    def on_search_finished(self, found_addresses, progress_dialog):
        """地址搜索完成后的处理"""
        # 关闭进度对话框
        progress_dialog.close()

        # 恢复UI状态
        self.disable_ui_during_search(False)

        # 处理搜索结果
        if found_addresses:
            result_text = f"搜索完成，找到有效地址: {found_addresses[0]}"
            QMessageBox.information(self, "搜索完成", result_text)
        else:
            QMessageBox.warning(self, "搜索完成", "未找到任何有效从站地址")

        # 清理线程
        self.address_search_thread = None

    def disable_ui_during_search(self, disable):
        """在搜索期间禁用/启用相关UI控件"""
        self.search_addr_btn.setEnabled(not disable)
        self.search_parity_btn.setEnabled(not disable)
        self.search_baudrate_btn.setEnabled(not disable)
        self.connect_btn.setEnabled(not disable)
        self.read_once_btn.setEnabled(not disable)
        self.auto_read_btn.setEnabled(not disable)
        self.save_data_btn.setEnabled(not disable)
        self.slave_addr_spin.setEnabled(not disable)
        self.port_combo.setEnabled(not disable)
        self.refresh_port_btn.setEnabled(not disable)
        self.baudrate_combo.setEnabled(not disable)
        self.parity_combo.setEnabled(not disable)

    def init_excel_file(self):
        """初始化Excel文件"""
        try:
            # 获取当前时间作为文件名，精确到毫秒
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
            self.current_excel_file = os.path.join(self.data_dir, f"压力数据_{timestamp}.xlsx")

            # 创建新的Excel文件并写入表头
            df = pd.DataFrame(columns=['时间戳(毫秒)', '压力值', '单位'])
            df.to_excel(self.current_excel_file, index=False)

            self.log_message(f"已初始化Excel数据文件: {self.current_excel_file}", "info")
        except Exception as e:
            self.log_message(f"初始化Excel文件失败: {str(e)}", "error")
            self.current_excel_file = None

    def connect_serial(self):
        """连接到串口，支持虚拟串口"""
        if self.port_combo.currentIndex() < 0:
            QMessageBox.warning(self, "选择串口", "请先选择一个串口号")
            return False

        # 获取实际的端口设备名（而不是显示的描述）
        port_name = self.port_combo.currentData()
        if not port_name:
            # 如果没有数据，使用显示文本的前半部分作为端口名
            display_text = self.port_combo.currentText()
            port_name = display_text.split(" - ")[0].strip()

        try:
            baudrate = int(self.baudrate_combo.currentText())

            # 转换校验方式
            parity_map = {
                "无校验": serial.PARITY_NONE,
                "奇校验": serial.PARITY_ODD,
                "偶校验": serial.PARITY_EVEN
            }
            parity = parity_map[self.parity_combo.currentText()]

            # 打开串口 (8N1)，增加超时时间以提高虚拟串口兼容性
            self.serial_port = serial.Serial(
                port=port_name,
                baudrate=baudrate,
                parity=parity,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                timeout=2,  # 增加超时时间
                write_timeout=2
            )

            if self.serial_port.is_open:
                self.log_message(f"已连接到 {port_name}，波特率 {baudrate}，校验方式 {self.parity_combo.currentText()}",
                                 "info")
                self.current_slave_addr = self.slave_addr_spin.value()
                return True
            else:
                self.log_message(f"无法打开串口 {port_name}", "error")
                return False

        except Exception as e:
            self.log_message(f"串口连接失败: {str(e)}", "error")
            # 针对虚拟串口连接失败提供更详细的提示
            self.log_message(f"如果这是虚拟串口，请确保虚拟串口驱动已正确安装且端口未被其他程序占用", "info")
            return False

    def disconnect_serial(self):
        """断开串口连接"""
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread = None

        if self.address_search_thread and self.address_search_thread.isRunning():
            self.address_search_thread.stop_search()
            self.address_search_thread = None

        if self.parity_search_thread and self.parity_search_thread.isRunning():
            self.parity_search_thread.stop_search()
            self.parity_search_thread = None

        if self.baudrate_search_thread and self.baudrate_search_thread.isRunning():
            self.baudrate_search_thread.stop_search()
            self.baudrate_search_thread = None

        if self.serial_port and self.serial_port.is_open:
            port_name = self.serial_port.port
            self.serial_port.close()
            self.serial_port = None
            self.log_message(f"已断开与 {port_name} 的连接", "info")

    def read_once(self):
        """读取一次数据"""
        if not self.serial_port or not self.serial_port.is_open:
            QMessageBox.warning(self, "未连接", "请先连接到串口")
            return

        # 如果已有线程在运行，先停止
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()

        # 启动新线程读取一次数据
        self.current_slave_addr = self.slave_addr_spin.value()
        self.serial_thread = SerialThread(
            self.serial_port,
            self.current_slave_addr,
            auto_read=False
        )
        self.serial_thread.data_received.connect(self.update_pressure_data)
        self.serial_thread.log_message.connect(self.log_message)
        self.serial_thread.status_changed.connect(self.statusBar().showMessage)
        self.serial_thread.start()

        self.statusBar().showMessage("正在读取数据...")

    def toggle_auto_read(self):
        """切换自动读取模式"""
        if not self.serial_port or not self.serial_port.is_open:
            QMessageBox.warning(self, "未连接", "请先连接到串口")
            return

        if self.serial_thread and self.serial_thread.isRunning() and self.serial_thread.auto_read:
            # 停止自动读取
            self.serial_thread.stop()
            self.serial_thread = None
            self.auto_read_btn.setText("自动读取")
            self.statusBar().showMessage("已停止自动读取")
        else:
            # 开始自动读取
            if self.serial_thread and self.serial_thread.isRunning():
                self.serial_thread.stop()

            self.current_slave_addr = self.slave_addr_spin.value()
            interval = self.read_interval_spin.value()
            self.serial_thread = SerialThread(
                self.serial_port,
                self.current_slave_addr,
                auto_read=True,
                read_interval=interval
            )
            self.serial_thread.data_received.connect(self.update_pressure_data)
            self.serial_thread.log_message.connect(self.log_message)
            self.serial_thread.status_changed.connect(self.statusBar().showMessage)
            self.serial_thread.start()

            self.auto_read_btn.setText("停止自动读取")
            self.statusBar().showMessage(f"已开始自动读取，间隔 {interval} 毫秒")

    def update_pressure_data(self, value, unit, timestamp, status, success):
        """更新压力数据显示，只有成功读取的数据才会被保存"""
        # 更新显示
        format_str = f"%.{self.serial_thread.decimal_places}f" if hasattr(self.serial_thread,
                                                                          'decimal_places') else "%.2f"
        self.pressure_display.setText(format_str % value)
        self.unit_display.setText(f"单位: {unit}")

        if hasattr(self.serial_thread, 'decimal_places'):
            self.decimal_display.setText(f"小数点位数: {self.serial_thread.decimal_places}")

        self.timestamp_display.setText(f"最后更新: {timestamp}")

        # 只有成功读取的数据才保存和更新波形
        if success:
            # 保存数据
            self.data_history.append((timestamp, value, unit))

            # 更新波形图
            self.update_plot(timestamp, value)

            # 自动保存
            if self.auto_save:
                self.save_data_to_excel(auto_save=True)

    def update_plot(self, timestamp, value):
        """更新波形图，确保能看到整体趋势"""
        # 转换时间戳为相对时间（秒），支持毫秒级精度
        dt = datetime.datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f")

        if not self.plot_start_time:
            self.plot_start_time = dt

        # 计算相对秒数（精确到毫秒）
        time_diff = dt - self.plot_start_time
        relative_seconds = time_diff.total_seconds()

        # 添加数据
        self.plot_x.append(relative_seconds)
        self.plot_y.append(value)
        self.plot_timestamps.append(timestamp)  # 保存完整时间戳用于标注

        # 限制数据点数量，防止内存溢出和显示过于拥挤
        max_points = 1000  # 增加可显示的数据点数量
        if len(self.plot_x) > max_points:
            # 均匀采样保留数据趋势，同时保留最新的100个点
            keep_recent = 100
            if len(self.plot_x) - keep_recent > 0:
                sampled_x = self.plot_x[:-keep_recent:len(self.plot_x) // (max_points - keep_recent)]
                sampled_y = self.plot_y[:-keep_recent:len(self.plot_x) // (max_points - keep_recent)]
                sampled_ts = self.plot_timestamps[:-keep_recent:len(self.plot_x) // (max_points - keep_recent)]

                # 添加最新的点
                sampled_x.extend(self.plot_x[-keep_recent:])
                sampled_y.extend(self.plot_y[-keep_recent:])
                sampled_ts.extend(self.plot_timestamps[-keep_recent:])

                self.plot_x = sampled_x
                self.plot_y = sampled_y
                self.plot_timestamps = sampled_ts

        # 更新曲线
        self.plot_line.set_xdata(self.plot_x)
        self.plot_line.set_ydata(self.plot_y)

        # 调整坐标轴范围以显示所有数据
        self.canvas.axes.relim()
        self.canvas.axes.autoscale_view()

        # 确保预览图始终显示完整波形
        if self.plot_x:
            x_margin = (max(self.plot_x) - min(self.plot_x)) * 0.05 if len(self.plot_x) > 1 else 1
            self.canvas.axes.set_xlim(min(self.plot_x) - x_margin, max(self.plot_x) + x_margin)

        if self.plot_y:
            y_margin = (max(self.plot_y) - min(self.plot_y)) * 0.05 if len(self.plot_y) > 1 else 0.1
            self.canvas.axes.set_ylim(min(self.plot_y) - y_margin, max(self.plot_y) + y_margin)

        # 刷新画布
        self.canvas.draw()

    def clear_plot(self):
        """清除波形图"""
        self.plot_x = []
        self.plot_y = []
        self.plot_timestamps = []
        self.plot_start_time = None
        self.plot_line.set_xdata([])
        self.plot_line.set_ydata([])
        self.canvas.axes.relim()
        self.canvas.axes.autoscale_view()
        self.canvas.draw()

    def show_plot_dialog(self):
        """显示波形图弹窗"""
        if not self.plot_x or not self.plot_y:
            QMessageBox.information(self, "无数据", "没有可显示的波形数据")
            return

        # 获取单位信息
        unit = self.unit_display.text().replace("单位: ", "") if self.unit_display.text() else "未知单位"

        # 创建并显示对话框，传入完整时间戳用于标注
        dialog = PlotDialog(
            self.plot_x,
            self.plot_y,
            self.plot_timestamps,
            title=f"压力波形图 ({unit})",
            y_label=f"压力值 ({unit})",
            parent=self
        )
        dialog.exec_()

    def import_from_excel(self):
        """从Excel文件导入数据并生成波形图"""
        # 打开文件选择对话框
        filename, _ = QFileDialog.getOpenFileName(
            self, "选择Excel文件",
            self.data_dir if hasattr(self, 'data_dir') else os.path.expanduser("~"),
            "Excel文件 (*.xlsx *.xls);;所有文件 (*)"
        )

        if not filename:
            return

        try:
            # 读取Excel文件
            df = pd.read_excel(filename)

            # 检查必要的列是否存在
            required_columns = ['时间戳(毫秒)', '压力值']
            if not all(col in df.columns for col in required_columns):
                QMessageBox.warning(self, "文件格式错误", "Excel文件必须包含'时间戳(毫秒)'和'压力值'列")
                return

            # 处理数据
            timestamps = df['时间戳(毫秒)'].tolist()
            pressure_values = df['压力值'].tolist()

            # 转换时间戳为相对时间
            if len(timestamps) < 2:
                QMessageBox.warning(self, "数据不足", "Excel文件中的数据不足，无法生成波形图")
                return

            try:
                # 转换为datetime对象，支持毫秒
                dt_list = [datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S.%f") for ts in timestamps]
                start_time = dt_list[0]
                relative_seconds = [(dt - start_time).total_seconds() for dt in dt_list]

                # 获取单位信息
                unit = df['单位'].iloc[0] if '单位' in df.columns else "未知单位"

                # 显示波形图
                dialog = PlotDialog(
                    relative_seconds,
                    pressure_values,
                    timestamps,
                    title=f"从Excel生成的压力波形图 ({os.path.basename(filename)})",
                    y_label=f"压力值 ({unit})",
                    parent=self
                )
                dialog.exec_()

            except Exception as e:
                QMessageBox.warning(self, "时间格式错误",
                                    f"时间戳格式错误: {str(e)}\n请确保时间格式为YYYY-MM-DD HH:MM:SS.fff")
                return

        except Exception as e:
            QMessageBox.critical(self, "读取失败", f"无法读取Excel文件:\n{str(e)}")

    def save_data_to_excel(self, auto_save=False):
        """保存数据到Excel（追加到同一个文件）"""
        if not self.data_history:
            if not auto_save:  # 自动保存时不弹窗
                QMessageBox.information(self, "无数据", "没有可保存的数据")
            return

        try:
            # 如果还没有Excel文件，创建一个
            if not self.current_excel_file:
                # 精确到毫秒的文件名
                timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
                self.current_excel_file = os.path.join(self.data_dir, f"压力数据_{timestamp}.xlsx")
                # 创建新文件并写入表头
                df = pd.DataFrame(columns=['时间戳(毫秒)', '压力值', '单位'])
                df.to_excel(self.current_excel_file, index=False)
                self.log_message(f"已创建新Excel数据文件: {self.current_excel_file}", "info")

            # 读取已有数据
            existing_df = pd.read_excel(self.current_excel_file)

            # 获取新数据（已存在的数据不再重复保存）
            new_data = []
            for entry in self.data_history:
                timestamp, value, unit = entry
                # 检查是否已存在
                if not ((existing_df['时间戳(毫秒)'] == timestamp) & (existing_df['压力值'] == value)).any():
                    new_data.append(entry)

            if not new_data:
                if not auto_save:
                    self.log_message("没有新数据需要保存", "info")
                return

            # 创建新数据的DataFrame
            new_df = pd.DataFrame(new_data, columns=['时间戳(毫秒)', '压力值', '单位'])

            # 追加到已有数据
            combined_df = pd.concat([existing_df, new_df], ignore_index=True)

            # 保存到Excel
            combined_df.to_excel(self.current_excel_file, index=False)

            if not auto_save:  # 自动保存时不弹窗，只记录日志
                self.log_message(f"已保存 {len(new_data)} 条新数据至: {self.current_excel_file}", "info")
                QMessageBox.information(self, "保存成功",
                                        f"已保存 {len(new_data)} 条新数据至:\n{self.current_excel_file}")
            else:
                self.log_message(f"自动保存 {len(new_data)} 条数据至: {self.current_excel_file}", "info")

        except Exception as e:
            self.log_message(f"保存数据失败: {str(e)}", "error")
            if not auto_save:  # 自动保存时不弹窗
                QMessageBox.critical(self, "保存失败", f"无法保存数据:\n{str(e)}")

    def clear_data(self):
        """清除历史数据"""
        reply = QMessageBox.question(
            self, "确认清除",
            "确定要清除所有历史数据吗？此操作不可恢复。",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            self.data_history = []
            self.clear_plot()
            self.log_message("已清除所有历史数据", "info")

    def toggle_auto_save(self, state):
        """切换自动保存状态"""
        self.auto_save = (state == Qt.Checked)
        status = "开启" if self.auto_save else "关闭"
        self.log_message(f"自动保存数据: {status}", "info")

        # 如果开启自动保存且已连接但还没有Excel文件，则初始化一个
        if self.auto_save and self.serial_port and self.serial_port.is_open and not self.current_excel_file:
            self.init_excel_file()

    def log_message(self, message, msg_type):
        """记录日志消息"""
        # 确保log_text已初始化
        if not hasattr(self, 'log_text'):
            return

        # 根据消息类型设置颜色
        color_map = {
            "receive": "#27ae60",  # 接收数据-绿色
            "send": "#3498db",  # 发送数据-蓝色
            "info": "#34495e",  # 信息-深灰色
            "error": "#e74c3c",  # 错误-红色
            "status": "#f39c12"  # 状态-橙色
        }

        color = color_map.get(msg_type, "#34495e")

        # 对状态消息特殊处理，更新底部状态栏而不是日志区域
        if msg_type == "status":
            self.device_status.setText(message)
            return

        # 添加到日志区域
        self.log_text.insertHtml(f'<span style="color:{color}">{message}</span><br>')
        # 滚动到底部
        self.log_text.moveCursor(QTextCursor.End)

    def closeEvent(self, event):
        """窗口关闭事件处理"""
        if self.serial_thread and self.serial_thread.isRunning():
            self.serial_thread.stop()

        if self.address_search_thread and self.address_search_thread.isRunning():
            self.address_search_thread.stop_search()

        if self.parity_search_thread and self.parity_search_thread.isRunning():
            self.parity_search_thread.stop_search()

        if self.baudrate_search_thread and self.baudrate_search_thread.isRunning():
            self.baudrate_search_thread.stop_search()

        if self.serial_port and self.serial_port.is_open:
            self.serial_port.close()

        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PressureMonitor()
    window.show()
    sys.exit(app.exec_())
